﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics.Eventing.Reader;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using Microsoft.Win32;
using WorldBankBBS.Common;
using WorldBankBBS.ICSharpCode.TextEditor.Src.Document;
using WorldBankBBS.ICSharpCode.TextEditor.Src.Document.HighlightingStrategy;
using WorldBankBBS.ICSharpCode.TextEditor.Src.Document.LineManager;
using WorldBankBBS.ICSharpCode.TextEditor.Src.Document.TextBufferStrategy;
using WorldBankBBS.Logic;
using WorldBankBBS.Translators;
using WorldBankBBS.Translators.PetsciiEncoder;
using IMPLTYPEFLAGS = System.Runtime.InteropServices.ComTypes.IMPLTYPEFLAGS;

namespace WorldBankBBS.RichEditor
{
    public class TextEditor
    {
        private IDocument _document = new DocumentFactory().CreateDocument();
        private ITranslator _translator = null;
        private Session _session = null;
        private int _leftColumn;
        private TextLocation _position;
        private int _topLine;

        public String Text { get { return _document.TextContent; } }
        public int Length { get { return _document.TextLength; } }
        public Size WindowSize { get; set; }
        public int Top { get; set; }
        public int Left { get; set; }

        public TextLocation Position
        {
            get { return _position; }
            set { _position = value; }
        }

        public TextEditor()
        {
            _document.HighlightingStrategy = new DefaultHighlightingStrategy();
            var highlighter = new DefaultHighlightingStrategy();
            highlighter.AddRuleSet(new HighlightRuleSet());
            _document.HighlightingStrategy = highlighter;

        }

        public bool LoadFromBuffer(byte[] buffer, int offset, int length)
        {
            var result = true;

            var gap = new GapTextBufferStrategy();
            gap.SetContent(ASCIIEncoding.ASCII.GetString(buffer, offset, length));

            _document = new DocumentFactory().CreateFromTextBuffer(gap);
            var highlighter = new DefaultHighlightingStrategy();
            highlighter.AddRuleSet(new HighlightRuleSet());
            _document.HighlightingStrategy = highlighter;
            _document.HighlightingStrategy.MarkTokens(_document);

            return result;
        }

        public bool Initialize(Session session,
            Field<string> field1,
            Field<DateTime> field2,
            Field<string> field3,
            Field<string> field4,
            int headerHeight, int footerHeight,
            Field<string> footer
            )
        {
            _document.LineCountChanged -= DocumentOnLineCountChanged;
            _document.LineCountChanged += DocumentOnLineCountChanged;
            _translator = session.Encoding.GetTranslator(session);
            _session = session;
            var screenWidth = (int)session["ScreenWidth"];
            var screenHeight = (int)session["ScreenHeight"];

            Top = headerHeight + 1;

            WindowSize = new Size(screenWidth - 1 - Left, screenHeight - headerHeight - footerHeight - 1);

            Position = new TextLocation(0, 0);
            var outbuffer = new byte[1024 * 256];
            int l;

            if (_session.Encoding != "ASCII")
            {
                ClearScreen();
                WriteHeaderBar(field1, field2, field3, field4,
                    headerHeight, footerHeight,
                    footer);
                //_position = _document.OffsetToPosition(_document.TextLength - 1);
                _position.Y = 0;
                _position.X = _leftColumn = 0;
                if (_document.TotalNumberOfLines > 1)
                {
                    var lastLine = _document.GetLineSegment(_document.TotalNumberOfLines - 1);
                    if (lastLine != null)
                    {
                        var builder = new StringBuilder();
                        foreach (var word in lastLine.Words)
                        {
                            builder.Append(word.Word.Trim());
                        }

                        if (builder.Length > 0)
                        {
                            _document.Insert(_document.TextLength, "\r\n");
                        }
                        
                        _position.X = 0;
                        _position.Y = _document.TotalNumberOfLines - 1;

                        _topLine = Math.Max(0, _document.TotalNumberOfLines - screenHeight);
                        
                        DrawText(false);
                    }
                }
                SetCaret(_position, false, true, false);
            }
            else
            {
                var ll = 0;
                for (ll = 0; ll < _document.TotalNumberOfLines; ++ll)
                {
                    AsciiDrawText(new TextLocation(0, ll));
                }

                l = _translator.StringToBytes(outbuffer, "{CRLF}{CRLF}" + ll.ToString("0000") + ":");
                session.Connection.SendData(outbuffer, 0, l);
            }

            var editing = true;
            var inClear = false;
            var inCommand = false;
            var timeoutSeconds = Convert.ToInt32(ConfigurationManager.AppSettings["InactivityTimeoutSeconds"]);

            while (editing)
            {
                var start = DateTime.Now;
                while (!_session.Connection.HasData && _session.Connection.IsConnected && DateTime.Now - start < new TimeSpan(0, 0, timeoutSeconds)) Thread.Sleep(10);

                if (!_session.Connection.IsConnected || DateTime.Now - start >= new TimeSpan(0, 0, timeoutSeconds))
                {
                    throw new SessionAbortedException(session);
                }

                var b = new byte[10];

                l = session.Connection.ReadData(b, 0, 10);
                switch (session.Encoding)
                {
                    case "ASCII":
                        #region ASCII
                        if (b[0] == '.' && _position.X == 0)
                        {
                            inCommand = true;
                            var bb = new byte[100];
                            var ll = _translator.StringToBytes(bb, "{CRLF}{CRLF}Enter Command (A, S, R): ");
                            _session.Connection.SendData(bb, 0, ll);
                        }
                        else if (inCommand)
                        {
                            if (b[0] == (byte)'s' || b[0] == (byte)'S')
                            {
                                l = _translator.StringToBytes(outbuffer, "Save{CRLF}{CRLF}{CRLF}");
                                session.Connection.SendData(outbuffer, 0, l);
                                return true;
                            }
                            else if (b[0] == (byte)'a' || b[0] == (byte)'A')
                            {
                                l = _translator.StringToBytes(outbuffer, "Abort{CRLF}{CRLF}{CRLF}");
                                session.Connection.SendData(outbuffer, 0, l);
                                return false;
                            }
                            else if (b[0] == (byte)'r' || b[0] == (byte)'R')
                            {
                                l = _translator.StringToBytes(outbuffer, "Refresh{CRLF}{CRLF}");
                                session.Connection.SendData(outbuffer, 0, l);
                                for (int i = 0; i < _document.TotalNumberOfLines; ++i)
                                {
                                    _position.Y = i;
                                    _position.X = _document.GetLineSegment(i).TotalLength;
                                    AsciiDrawText(_position);
                                }
                            }
                            else
                            {
                                inCommand = false;
                                l = _translator.StringToBytes(outbuffer, "{CRLF}");
                                session.Connection.SendData(outbuffer, 0, l);
                                AsciiDrawText(_position);
                                break;
                            }
                        }
                        else if (b[0] == 3)
                        {
                            if (!inClear) inClear = true;
                            else
                            {
                                _document.Remove(0, _document.TextLength);
                                l = _translator.StringToBytes(outbuffer, TextEditorStrings.TEXTED_ASCII_CLEARED);
                                session.Connection.SendData(outbuffer, 0, l);
                                inClear = false;
                            }
                        }
                        else if (b[0] == 26)
                        {
                            if (_document.UndoStack.CanUndo)
                            {
                                _document.UndoStack.Undo();
                                l = _translator.StringToBytes(outbuffer, "{CRLF}");
                                session.Connection.SendData(outbuffer, 0, l);
                                AsciiDrawText(_position);
                            }
                        }
                        else if (b[0] == 8)
                        {
                            var lineCount = _document.TotalNumberOfLines;
                            if (_document.PositionToOffset(_position) > 0 && _position.X > 0)
                            {
                                _document.Remove(_document.PositionToOffset(_position) - 1, 1);
                                _position.X--;
                                session.Connection.SendData(b, 0, 1);
                                session.Connection.SendData(new[] { (byte)32 }, 0, 1);
                                session.Connection.SendData(b, 0, 1);
                            }
                            else if (_position.X == 0 && _position.Y > 0)
                            {
                                _position = _document.OffsetToPosition(_document.PositionToOffset(_position) - 1);
                                _document.Remove(_document.PositionToOffset(_position), 2);
                                _position.X--;
                                l = _translator.StringToBytes(outbuffer, "{CRLF}");
                                session.Connection.SendData(outbuffer, 0, l);
                                AsciiDrawText(_position);
                            }
                            // TODO: May need display logic here
                        }
                        else if (b[0] == (byte)'\r')
                        {
                            b[1] = (byte)'\n';
                            _document.Insert(_document.PositionToOffset(_position),
                                    ASCIIEncoding.ASCII.GetString(b, 0, 2));
                            _position.X = 0;
                            _leftColumn = 0;
                            _position.Y++;
                            l = _translator.StringToBytes(outbuffer, "{CRLF}");
                            session.Connection.SendData(outbuffer, 0, l);
                            AsciiDrawText(_position);
                        }
                        else if (b[0] < 27) break;
                        else
                        {
                            var ascii = new ASCIIEncoding();
                            var chars = new char[10];
                            ascii.GetDecoder().GetChars(b, 0, 1, chars, 0, true);
                            var s = new string(chars, 0, 1);

                            _document.Insert(Math.Max(0, _document.PositionToOffset(_position)), s);

                            _position.X++;

                            if (_position.X >= WindowSize.Width - 6)
                            {
                                // Word wrap
                                var line = _document.GetLineSegment(_position.Line);
                                var insertPoint = 0;

                                foreach (var w in line.Words)
                                {
                                    if (_document.OffsetToPosition(w.Offset).X + w.Length >=
                                        WindowSize.Width - 6)
                                    {
                                        //stack.Push(w);
                                        insertPoint = w.Offset;
                                        _document.Insert(insertPoint, Environment.NewLine);
                                        var back = new byte[w.Length * 2];
                                        for (int j = 0; j < back.Length / 2; ++j)
                                        {
                                            back[j] = 8;
                                            back[back.Length - j - 1] = 32;
                                        }
                                        _session.Connection.SendData(back);
                                        break;
                                    }
                                }
                                if (insertPoint == 0) _document.Insert(_document.PositionToOffset(_position), Environment.NewLine);
                                _position.Y++;
                                if (_position.Line < _document.TotalNumberOfLines)
                                {
                                    _position.X = _document.GetLineSegment(_position.Line).TotalLength;
                                }
                                else
                                {
                                    _position.X = 0;
                                }
                                l = _translator.StringToBytes(outbuffer, "{CRLF}");
                                session.Connection.SendData(outbuffer, 0, l);
                                AsciiDrawText(_position);
                            }
                            else
                            {
                                session.Connection.SendData(b, 0, 1);
                            }
                        }
                        break;
                        #endregion
                    case "ANSI":
                        #region ANSI
                        if (b[0] == 1)
                        {
                            SetCaret(new TextLocation(0, Math.Min(WindowSize.Height, _document.TotalNumberOfLines)), true);
                            return false;
                            break;
                        }
                        if (b[0] == 3)
                        {
                            if (!inClear) inClear = true;
                            else
                            {
                                _document.Remove(0, _document.TextLength);
                                _position = new TextLocation(0, 0);
                                DrawText(false); // Clear screen first.
                                SetCaret(_position, true, true);
                                inClear = false;
                            }
                            break;
                        }
                        if (b[0] == 26)
                        {
                            if (_document.UndoStack.CanUndo)
                            {
                                _document.UndoStack.Undo();
                                DrawText(false); // Clear screen first.
                                SetCaret(_position, true);
                            }
                            break;
                        }
                        if (b[0] == 19)
                        {
                            SetCaret(new TextLocation(0, Math.Min(WindowSize.Height, _document.TotalNumberOfLines)), true);
                            return true;
                        }
                        if (b[0] == 8)
                        {
                            var lineCount = _document.TotalNumberOfLines;
                            var off = _document.PositionToOffset(_position);
                            if (off > 0 && _position.X > 0)
                            {
                                _document.Remove(off - 1, 1);
                                off--;
                            }
                            else if (_position.X == 0 && _position.Y > 0)
                            {
                                _document.Remove(off - 2, 2);
                                off-=2;
                            }
                            _position = _document.OffsetToPosition(off);
                            if (_position.X > WindowSize.Width) _position.X = WindowSize.Width;
                            if (lineCount != _document.TotalNumberOfLines)
                            {
                                DrawText(false); // Clear screen first
                                SetCaret(_position, true, true);
                            }
                            else
                            {
                                DrawText(true);
                                SetCaret(_position, false, false, true);
                            }
                        }
                        if (b[0] == (byte)'\r')
                        {
                            b[1] = (byte)'\n';
                            _document.Insert(_document.PositionToOffset(_position),
                                    ASCIIEncoding.ASCII.GetString(b, 0, 2));
                            _position.X = 0;
                            _leftColumn = 0;
                            _position.Y++;
                            if (_position.Y - WindowSize.Height > _topLine)
                            {
                                //_position.Y-=3;
                                _topLine += 3;
                            }
                            DrawText(false); // Clear Screen First
                            SetCaret(_position, true, true);
                        }
                        if (b[0] < 27) break;
                        if (b[0] == 27)
                        {
                            // Up
                            if (b[1] == 'A' || b[2] == 'A')
                            {
                                if (Position.Y > 0)
                                {
                                    _position.Y -= 1;
                                    if (_position.Y < _topLine)
                                    {
                                        _topLine = _position.Y;
                                        DrawText(false); // Clear Screen First
                                    }
                                    SetCaret(_position, false, true, false);
                                }
                            }
                            // Down
                            if (b[1] == 'B' || b[2] == 'B')
                            {
                                if (Position.Y < _document.TotalNumberOfLines - 1)
                                {
                                    if (_position.X >
                                        _document.GetLineSegment(_position.Y + 1).Length)
                                    {
                                        _position.X = _document.GetLineSegment(_position.Y + 1 + _topLine).Length;
                                        DrawText(false); // Clear Screen First
                                    }
                                    if (_position.Y >= _topLine + WindowSize.Height)
                                    {
                                        _topLine++;
                                        DrawText(false);
                                    }
                                    _position.Y += 1;
                                    SetCaret(_position, true, true, false);
                                }
                            }
                            // Right
                            if (b[1] == 'C' || b[2] == 'C')
                            {
                                if (_position.X < WindowSize.Width)
                                {
                                    _position.X++;
                                    SetCaret(_position, false, false, false);
                                }
                                else if (_position.X == _leftColumn + WindowSize.Width)
                                {
                                    //++_leftColumn;
                                    ++_position.X;
                                    DrawText(true);
                                    SetCaret(_position, false, false, true);
                                }
                                else
                                {
                                    DrawText(true);
                                    SetCaret(_position, false, false, true);
                                }
                            }
                            // Left
                            if (b[1] == 'D' || b[2] == 'D')
                            {
                                if (_position.X > 0)
                                {
                                    _position.X--;
                                    //DrawText(false);
                                    SetCaret(_position, true);
                                }
                                else if (_position.X == 0 && _leftColumn > 0)
                                {
                                    --_leftColumn;
                                    DrawText(true);
                                    SetCaret(_position, false, false, true);
                                }
                            }

                            // Home
                            if (b[1] == 91 && b[2] == 72)
                            {
                                if (_position.X != _leftColumn)
                                    _position.X = _leftColumn;
                                else
                                {
                                    _position.X = _leftColumn = 0;
                                    DrawText(true);
                                }

                                SetCaret(_position, false, false, true);
                            }

                            // End
                            if (b[1] == 91 && b[2] == 75)
                            {
                                _position.X = _document.GetLineSegment(_position.Y).Length;
                                //_leftColumn = _position.X - WindowSize.Width;
                                DrawText(true);
                                SetCaret(_position, true, false, true);
                            }
                        }
                        else if (b[0] == 0x7f)
                        {
                            var offset = _document.PositionToOffset(_position);
                            if (offset == -1 && _document.TextLength > 0) offset = 0;
                            if (offset > -1 && offset < _document.TextLength)
                            {
                                var lineCount = _document.TotalNumberOfLines;
                                _document.Remove(offset, 1);
                                var newPosition = _document.OffsetToPosition(offset);
                                if (newPosition != _position || lineCount != _document.TotalNumberOfLines)
                                {
                                    DrawText(false); // Clear Screen First
                                    SetCaret(newPosition, true, true);
                                    _position = newPosition;
                                }
                                else
                                {
                                    DrawText(true);
                                    SetCaret(_position, false, false, true);
                                }
                            }
                        }
                        else
                        {
                            var offset = _document.PositionToOffset(_position);
                            if (_document.TextLength > 0 && _document.TextLength > offset)
                            {
                                if (_position.X - _leftColumn >= WindowSize.Width - 1)
                                {
                                    if (CheckForWrap(b[0]))
                                    {
                                        offset = _document.PositionToOffset(_position);
                                    }
                                    else
                                    {
                                        _position.X++;
                                    }

                                    _document.Insert(Math.Max(0, offset), ASCIIEncoding.ASCII.GetString(b, 0, 1));
                                }
                                else
                                {
                                    if (CheckForWrap(b[0]))
                                    {
                                        offset = _document.PositionToOffset(_position);
                                    }
                                    else
                                    {
                                        _position.X++;
                                    }

                                    var o = Math.Max(0, offset);
                                    var s = ASCIIEncoding.ASCII.GetString(b, 0, 1);
                                    _document.Replace(o, 1, s);
                                }
                            }
                            else
                            {
                                if (CheckForWrap(b[0]))
                                {
                                    offset = _document.PositionToOffset(_position);
                                }
                                else
                                {
                                    _position.X++;
                                }

                                _document.Insert(Math.Max(0, offset),
                                    ASCIIEncoding.ASCII.GetString(b, 0, 1));
                            }

                            _session.Connection.SendData(b, 0, 1);
                        }
                        _lastPosition.X = _position.X;
                        _lastPosition.Y = _position.Y;
                        break;
                        #endregion
                    case "PETSCII":
                        #region PETSCII
                        if (b[0] == 1)
                        {
                            DrawText(false);
                            SetCaret(new TextLocation(0, Math.Min(WindowSize.Height, _document.TotalNumberOfLines)), true);
                            return false;
                            break;
                        }
                        else if (b[0] == '.' && _position.X == 0)
                        {
                            inCommand = true;
                            var bb = new byte[100];
                            var ll = _translator.StringToBytes(bb,
                                "{HOME}{%DOWN10}{%DOWN10}{%DOWN3}{YELLOW}E{ORANGE}nter {YELLOW}C{ORANGE}ommand {WHITE}({MGRAY}A{WHITE},{MGRAY}S{WHITE},{MGRAY}R{WHITE})");
                            _session.Connection.SendData(bb, 0, ll);
                        }
                        else if (inCommand)
                        {
                            if (b[0] == (byte)'s' || b[0] == (byte)'S')
                            {
                                SetCaret(new TextLocation(0, Math.Min(WindowSize.Height, _document.TotalNumberOfLines)), true);
                                return true;
                            }
                            else if (b[0] == (byte)'a' || b[0] == (byte)'A')
                            {
                                SetCaret(
                                    new TextLocation(0, Math.Min(WindowSize.Height, _document.TotalNumberOfLines)), true);
                                return false;
                            }
                            else if (b[0] == (byte)'r' || b[0] == (byte)'R')
                            {
                                inCommand = false;
                                DrawText(false); // Clear Screen First
                                SetCaret(_position, true, false, false);
                            }
                            else
                            {
                                inCommand = false;
                                DrawText(false);
                                SetCaret(_position, true, false);
                                break;
                            }
                        }
                        else if (b[0] == 3)
                        {
                            if (!inClear) inClear = true;
                            else
                            {
                                _document.Remove(0, _document.TextLength);
                                _position = new TextLocation(0, 0);
                                DrawText(false); // Clear Screen First
                                SetCaret(_position, true, true);
                                inClear = false;
                            }
                            break;
                        }
                        else if (b[0] == 26)
                        {
                            if (_document.UndoStack.CanUndo)
                            {
                                _document.UndoStack.Undo();
                                DrawText(false);
                                SetCaret(_position, true);
                            }
                            break;
                        }
                        else if (b[0] == 8)
                        {
                            var offset = _document.PositionToOffset(_position);
                            if (offset == -1 && _document.TextLength > 0) offset = 0;
                            if (offset > -1 && offset < _document.TextLength)
                            {
                                var lineCount = _document.TotalNumberOfLines;
                                _document.Remove(offset, 1);
                                var newPosition = _document.OffsetToPosition(offset);
                                if (newPosition != _position || lineCount != _document.TotalNumberOfLines)
                                {
                                    DrawText(false); // Clear Screen First
                                    SetCaret(newPosition, true, true);
                                    _position = newPosition;
                                }
                                else
                                {
                                    DrawText(true);
                                    SetCaret(_position, false, false, true);
                                }
                            }
                        }
                        else if (b[0] == 20)
                        {
                            var lineCount = _document.TotalNumberOfLines;
                            var off = _document.PositionToOffset(_position);
                            if (off > 0 && _position.X > 0)
                            {
                                _document.Remove(off - 1, 1);
                                off--;
                            }
                            else if (_position.X == 0 && _position.Y > 0)
                            {
                                _document.Remove(off - 2, 2);
                                off -= 2;
                            }
                            _position = _document.OffsetToPosition(off);
                            if (_position.X > WindowSize.Width) _position.X = WindowSize.Width;
                            if (lineCount != _document.TotalNumberOfLines)
                            {
                                DrawText(false); // Clear screen first
                                SetCaret(_position, true, true);
                            }
                            else
                            {
                                DrawText(true);
                                SetCaret(_position, false, false, true);
                            }
                        }
                        else if (b[0] == (byte)'\r')
                        {
                            b[1] = (byte)'\n';
                            _document.Insert(_document.PositionToOffset(_position),
                                    ASCIIEncoding.ASCII.GetString(b, 0, 2));
                            _position.X = 0;
                            _leftColumn = 0;
                            _position.Y++;
                            if (_position.Y - WindowSize.Height > _topLine)
                            {
                                //_position.Y-=3;
                                _topLine += 3;
                            }
                            DrawText(false); // Clear Screen First
                            SetCaret(_position, true, true);
                        }
                        else if (b[0] == 17)
                        {
                            if (Position.Y < _document.TotalNumberOfLines - 1)
                            {
                                if (_position.X >
                                    _document.GetLineSegment(_position.Y + 1).Length)
                                {
                                    _position.X = _document.GetLineSegment(_position.Y + 1 + _topLine).Length;
                                    DrawText(false); // Clear Screen First
                                }
                                if (_position.Y >= _topLine + WindowSize.Height)
                                {
                                    _topLine++;
                                    DrawText(false);
                                }
                                _position.Y += 1;
                                SetCaret(_position, true, true, false);
                            }
                        }
                        else if (b[0] == 19)
                        {
                            if (_position.X != _leftColumn)
                                _position.X = _leftColumn;
                            else
                            {
                                _position.X = _leftColumn = 0;
                                DrawText(true);
                            }

                            SetCaret(_position, false, false, true);
                        }
                        else if (b[0] == 147)
                        {
                            _position.X = _document.GetLineSegment(_position.Y).Length;
                            //_leftColumn = _position.X - WindowSize.Width;
                            DrawText(true);
                            SetCaret(_position, true, false, true);
                        }
                        else if (b[0] < 27) break;
                        else if (b[0] == 29)
                        {
                            if (_position.X < WindowSize.Width)
                            {
                                _position.X++;
                                SetCaret(_position, false, false, false);
                            }
                            else if (_position.X == _leftColumn + WindowSize.Width)
                            {
                                //++_leftColumn;
                                ++_position.X;
                                DrawText(true);
                                SetCaret(_position, false, false, true);
                            }
                            else
                            {
                                DrawText(true);
                                SetCaret(_position, false, false, true);
                            }
                        }
                        else if (b[0] == 145)
                        {
                            if (Position.Y > 0)
                            {
                                _position.Y -= 1;
                                if (_position.Y < _topLine)
                                {
                                    _topLine = _position.Y;
                                    DrawText(false); // Clear Screen First
                                }
                                SetCaret(_position, false, true, false);
                            }
                        }
                        else if (b[0] == 157)
                        {
                            if (_position.X > 0)
                            {
                                _position.X--;
                                //DrawText(false);
                                SetCaret(_position, true);
                            }
                            else if (_position.X == 0 && _leftColumn > 0)
                            {
                                --_leftColumn;
                                DrawText(true);
                                SetCaret(_position, false, false, true);
                            }
                        }
                        else
                        {
                            var ascii = new ASCIIEncoding();
                            var petscii = new Petscii();
                            var chars = new char[10];
                            petscii.GetDecoder().GetChars(b, 0, 1, chars, 0, true);
                            var s = new string(chars, 0, 1);
                            var offset = _document.PositionToOffset(_position);

                            //var abytes = Petscii.Convert(petscii, ascii, petscii.GetBytes(new [] { (char)b[0] }, 0, 1));

                            if (_document.TextLength > 0 && _document.TextLength > offset)
                            {
                                if (_position.X - _leftColumn >= WindowSize.Width - 1)
                                {
                                    if (CheckForWrap(b[0]))
                                    {
                                        offset = _document.PositionToOffset(_position);
                                    }
                                    else
                                    {
                                        _position.X++;
                                    }

                                    _document.Insert(Math.Max(0, offset), s);
                                }
                                //else if (_document.TextLength == 0)
                                //{
                                //    CheckForWrap(b[0]);

                                //    _document.Insert(0, s);
                                //}
                                else
                                {
                                    if (CheckForWrap(b[0]))
                                    {
                                        offset = _document.PositionToOffset(_position);
                                    }
                                    else
                                    {
                                        _position.X++;
                                    }

                                    var o = Math.Max(0, offset);
                                    _document.Replace(o, 1, s);
                                }
                            }
                            else
                            {
                                if (CheckForWrap(b[0]))
                                {
                                    offset = _document.PositionToOffset(_position);
                                }
                                //else
                                {
                                    _position.X++;
                                }

                                _document.Insert(Math.Max(0, offset), s);
                            }
                            
                            _session.Connection.SendData(b, 0, 1);
                        }
                        _lastPosition.X = _position.X;
                        _lastPosition.Y = _position.Y;
                        break;
                        #endregion
                }
            }

            return false;
        }

        protected bool CheckForWrap(byte b)
        {
            var currentLine = _position.Y;
            if (_position.X >= WindowSize.Width && 
                b != 32 && b != 13 && b != 10)
            {
                var line = _document.GetLineSegment(_position.Y);

                if (line != null)
                {
                    var words = line.Words.Count;

                    if (words > 0)
                    {
                        var lastWord = line.Words[words - 1];

                        if (lastWord.Offset > 0)
                        {
                            var o = line.Offset + lastWord.Offset;

                            if (o > -1)
                            {
                                _document.Insert(o, "\r\n");

                            }
                            _position.X = lastWord.Word.Length;
                            _position.Y = currentLine + 1;
                            DrawText(false);
                            _position.Y = currentLine + 1;
                            SetCaret(_position, false, true, false);

                            return true;
                        }
                        else
                        {
                            // OK, so the scenario here is that the user has
                            // typed a line of all non-whitespace characters.
                            // We will add a break at the end of the line and 
                            // then move to the next line.  However, we do NOT
                            // return true because we want the code that called
                            // this method to increment the X position after
                            // displaying the character.
                            var o = line.Offset + line.TotalLength;

                            if (o > -1)
                            {
                                _document.Insert(o, "\r\n");

                            }
                            _position.X = 0;
                            _position.Y = currentLine + 1;
                            DrawText(false);
                            _position.Y = currentLine + 1;
                            SetCaret(_position, false, true, false);

                            return false;
                        }
                    }
                }
            }
            return false;
        }

        private void AsciiDrawText(TextLocation position)
        {
            int l;
            var buffer = new byte[1024];
            if (position.Line < _document.TotalNumberOfLines - 1)
            {
                var line = _document.LineSegmentCollection[position.Line];
                var startPosition = _document.PositionToOffset(new TextLocation(0, line.LineNumber));
                var endPosition = _document.PositionToOffset(new TextLocation(0, line.LineNumber + 1));
                l = _translator.StringToBytes(buffer, string.Format("{0:0000}: ", position.Line + 1) + _document.TextContent.Substring(startPosition, endPosition - startPosition));
            }
            else if (position.Line == _document.TotalNumberOfLines - 1)
            {
                var line = _document.LineSegmentCollection[position.Line];
                var startPosition = _document.PositionToOffset(new TextLocation(0, line.LineNumber));
                l = _translator.StringToBytes(buffer, string.Format("{0:0000}: ", position.Line + 1) + _document.TextContent.Substring(startPosition, _document.TextLength - startPosition));
            }
            else
            {
                l = _translator.StringToBytes(buffer, string.Format("{0:0000}: ", position.Line + 1));
            }
            _session.Connection.SendData(buffer, 0, Math.Min(l, (int)_session["ScreenWidth"] - 1));
        }

        private void DocumentOnLineCountChanged(object sender, LineCountChangeEventArgs lineCountChangeEventArgs)
        {
            //_lineCount = lineCountChangeEventArgs.Document.TotalNumberOfLines;
        }

        TextLocation _lastPosition = new TextLocation(0, 0);

        private void SetCaret(TextLocation position, bool forceDraw = true, bool clearFirst = false, bool currentLineOnly = false)
        {
            var sb = new StringBuilder();

            if (clearFirst)
            {
                sb.Append("{HOME}");
                for (int y = 0; y < Top; ++y) sb.Append("{DOWN}");

                for (int x = _leftColumn; x < position.Column; ++x)
                {
                    sb.Append("{RIGHT}");
                }
                for (int y = _topLine; y < position.Line; ++y)
                {
                    sb.Append("{DOWN}");
                }
            }
            else if (currentLineOnly)
            {
                sb.Append("{%BOL}");
                for (int x = _leftColumn; x < position.Column; ++x)
                {
                    sb.Append("{RIGHT}");
                }
            }
            else
            {
                if (position.X < _lastPosition.X)
                {
                    for (int x = position.X; x < _lastPosition.X; ++x)
                    {
                        sb.Append("{LEFT}");
                    }
                }
                else if (position.X > _lastPosition.X)
                {
                    for (int x = _lastPosition.X; x < position.X; ++x)
                    {
                        sb.Append("{RIGHT}");
                    }
                }

                if (position.Y < _lastPosition.Y)
                {
                    for (int y = position.Y; y < _lastPosition.Y; ++y)
                    {
                        sb.Append("{UP}");
                    }
                }
                else if (position.Y > _lastPosition.Y)
                {
                    for (int y = _lastPosition.Y; y < position.Y; ++y)
                    {
                        sb.Append("{DOWN}");
                    }
                }
            }

            var b = new byte[512];
            var l = _translator.StringToBytes(b, sb.ToString());
            _session.Connection.SendData(b, 0, l, l);
            _lastPosition = position;
        }

        private void DrawText(bool currentLineOnly = false)
        {
            var b = new byte[1024];
            if (!currentLineOnly)
            {
                var builder = new StringBuilder();
                builder.Append("{HOME}");
                for (int y = 0; y < Top; ++y) builder.Append("{DOWN}");

                //for (int y = Top; y < _position.Y - Top; ++y)
                //{
                //    builder.Append("{DOWN}");
                //}

                builder.Append("{%BOL}");
                _translator.StringToBytes(b, builder.ToString());
            }
            else
            {
                var builder = new StringBuilder();
                builder.Append("{HOME}");
                for (int y = 0; y < Top; ++y) builder.Append("{DOWN}");

                for (int y = _topLine; y < _position.Y - _topLine; ++y)
                {
                    builder.Append("{DOWN}");
                }

                builder.Append("{%BOL}");
                _translator.StringToBytes(b, builder.ToString());
            }

            _session.Connection.SendData(b);

            for (int i = 0; i < WindowSize.Height; ++i)
            {
                if (i + _topLine < _document.TotalNumberOfLines)
                {
                    if (currentLineOnly && i + _topLine != _position.Y) continue;

                    var line = _document.GetLineSegment(i + _topLine);
                    var position = 0;

                    TextWord word = null;
                    int j = 0;

                    for (; j < line.Words.Count; ++j)
                    {
                        word = line.Words[j];
                        var space = (word.Word == "" && word.Length == 1 ? " " : ""); // (j < line.Words.Count - 1 ? "" : "");
                        position += word.Length;
                        if (position >= WindowSize.Width) break;

                        if (_translator is CbmTranslator)
                        {
                            var ascii = new ASCIIEncoding();
                            var petscii = new Petscii();
                            var pbytes = Petscii.Convert(ascii, petscii, ascii.GetBytes(word.Word + space));
                            _session.Connection.SendData(pbytes, 0, WindowSize.Width);
                        }
                        else
                        {
                            var pbytes =
                                ASCIIEncoding.ASCII.GetBytes(word.Word + space);
                            _session.Connection.SendData(pbytes, 0, WindowSize.Width);
                        }
                    }

                    if (word != null && j < line.Words.Count)
                    {
                        //if (word.Word.Length > 0)
                        {
                            var partialWord = word.Word.Substring(0, word.Word.Length - (position - WindowSize.Width));
                            var space = (word.Word == "" && word.Length == 1 ? " " : ""); // (j < line.Words.Count - 1 ? "" : "");
                            partialWord += space;
                            if (partialWord.Length > 0)
                            {
                                if (_translator is CbmTranslator)
                                {
                                    var ascii = new ASCIIEncoding();
                                    var petscii = new Petscii();
                                    var pbytes = Petscii.Convert(ascii, petscii, ascii.GetBytes(partialWord + space));
                                    _session.Connection.SendData(pbytes, 0, WindowSize.Width);
                                }
                                else
                                {
                                    _session.Connection.SendData(
                                        ASCIIEncoding.ASCII.GetBytes(partialWord), 0, WindowSize.Width);
                                }
                            }
                        }
                    }
                    else
                    {
                        var spaces = new StringBuilder();
                        for (int k = 0; k + position < _leftColumn + WindowSize.Width && k < WindowSize.Width; ++k)
                        {
                            spaces.Append(" ");
                        }
                        _session.Connection.SendData(
                            ASCIIEncoding.ASCII.GetBytes(spaces.ToString()), 0, WindowSize.Width);
                    }
                    if (!currentLineOnly)
                    {
                        var l = _translator.StringToBytes(b, "{CRLF}");
                        _session.Connection.SendData(b, 0, l);
                    }

                }
            }
        }


        public void ClearScreen()
        {
            var buffer = new byte[10];

            var l = _translator.StringToBytes(buffer, "{CLEAR}");
            _session.Connection.SendData(buffer, 0, l);
        }

        public class Field<T>
        {
            public Field(int x, int y) { Location = new Point(x, y); }
            public Point Location { get; set; }
            public T Value { get; set; }
            public string Format { get; set; }
            public string LabelText { get; set; }
            public string LabelModifiers { get; set; }
            public string ValueModifiers { get; set; }
            public string Calculated { get { return (LabelText ?? string.Empty) + string.Format("{" + Format + "}", Value); } }
        }

        public void WriteHeaderBar(
            Field<string> field1,
            Field<DateTime> field2,
            Field<string> field3,
            Field<string> field4,
            int headerHeight, int footerHeight,
            Field<string> footer
            )
        {

            var buffer2 = new byte[1024];
            var b = new byte[7];
            var hll = _translator.StringToBytes(b, "{HLINE}");
            var hline = b[0];
            var bl = _translator.StringToBytes(b, "{BTEE}");
            var btee = b[0];
            var hol = _translator.StringToBytes(b, "{HOME}");
            var home = b.ToArray();
            var dwn = _translator.StringToBytes(b, "{DOWN}");
            var down = b.ToArray();
            var yll = _translator.StringToBytes(b, "{YELLOW}");
            var yellow = b.ToArray();

            var screenWidth = (int)_session["ScreenWidth"];

            var headerBuffer = new char[1024];

            var l = 0;

            int start = 0;

            if (field1 != null)
            {
                DisplayField(field1, headerBuffer, buffer2);
            }

            if (field2 != null)
            {
                DisplayField(field2, headerBuffer, buffer2);
            }

            if (field3 != null)
            {
                DisplayField(field3, headerBuffer, buffer2);
            }

            if (field4 != null)
            {
                DisplayField(field4, headerBuffer, buffer2);
            }

            DisplayField(new Field<string>(0, headerHeight) { Value = " ", Format = "0" }, headerBuffer, buffer2);

            int i = _leftColumn;
            int c = 0;
            buffer2 = new byte[81];
            for (; i < _leftColumn + WindowSize.Width; ++i)
            {
                if (i % 5 == 0)
                {
                    buffer2[i - _leftColumn] = btee;
                }
                else
                {
                    buffer2[i - _leftColumn] = hline;
                }
                ++c;
            }

            var list = new List<byte>();
            for (; c >= 0; --c)
            {
                list.Insert(0, buffer2[c]);
            }
            list.Add(32);

            buffer2 = list.ToArray();

            _session.Connection.SendData(buffer2, 0, buffer2.Length);

        }

        private void DisplayField<T>(Field<T> field, char[] buffer, byte[] buffer2)
        {
            for (int k = 0; k < buffer.Length; ++k)
            {
                buffer[k] = ' ';
            }

            if (field != null /* && !string.IsNullOrWhiteSpace(field.Calculated) */)
            {
                for (int j = 0; j < "{HOME}".Length; ++j)
                {
                    buffer[j] = "{HOME}"[j];
                }
                for (int j = 0; j < field.Location.Y; ++j)
                {
                    for (int k = 0; k < "{DOWN}".Length; ++k)
                    {
                        buffer["{HOME}".Length + "{DOWN}".Length * j + k] = "{DOWN}"[k];
                    }
                }
                var start = "{HOME}".Length + "{DOWN}".Length * field.Location.Y;
                for (int j = 0; j < field.Location.X; ++j)
                {
                    for (int k = 0; k < "{RIGHT}".Length; ++k)
                    {
                        buffer[start + k + j * "{RIGHT}".Length] = "{RIGHT}"[k];
                    }
                }

                start = "{HOME}".Length + "{DOWN}".Length * field.Location.Y + "{RIGHT}".Length * field.Location.X;
                for (int j = 0; j < field.Calculated.Length; ++j)
                {
                    buffer[start + j] = field.Calculated[j];
                }

                var l = _translator.StringToBytes(buffer2, string.Join("", buffer).Trim());
                _session.Connection.SendData(buffer2, 0, l);
            }

        }
    }
}
